package in.iqing.module.download;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;

import com.alibaba.fastjson.JSON;
import com.android.volley.RequestQueue;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import in.iqing.Constants;
import in.iqing.control.api.ApiHelper;
import in.iqing.control.api.callback.ParagraphCallback;
import in.iqing.control.helper.EventHelper;
import in.iqing.control.helper.ImageResourceHelper;
import in.iqing.control.helper.LogHelper;
import in.iqing.control.util.MiscUtil;
import in.iqing.model.bean.Book;
import in.iqing.model.bean.Chapter;
import in.iqing.model.bean.Paragraph;
import in.iqing.model.bean.Volume;
import in.iqing.model.db.DBHelper;

/**
 * Created by ZuoShu on 2015/7/16.
 */
public class DownloadManager {
    private static final String TAG = "DownloadManager";
    private static DownloadManager mInstance;

    private Handler workHandler;
    private HandlerThread workThread;

    private static final int DOWNLOAD_BOOK = 1;
    private static final int DOWNLOAD_CHAPTER = 2;
    private static final int STOP_DOWNLOAD_BOOK = 3;
    private static final int DELETE_BOOK = 4;
    private static final int DOWNLOAD_BOOK_COVER = 5;

    private static final int MAX_RETRY = 5;
    private ReadContentCallback readContentCallback;
    private Object readContentMutex = new Object();
    private List<Paragraph> chapterParagraphs;
    private int retry;
    private RequestQueue requestQueue = null;
    // stop download book will add bookid to stoppedBook
    private Set<Integer> stoppedBook;

    public static synchronized DownloadManager getInstance() {
        if (mInstance == null) {
            mInstance = new DownloadManager();
        }
        return mInstance;
    }

    private DownloadManager() {
        workThread = new HandlerThread("Download");
        workThread.start();
        workHandler = new WorkHandler(workThread.getLooper());
        readContentCallback = new ReadContentCallback();
        stoppedBook = new HashSet<>();
    }

    public void start(Book book) {
        removeStoppedBook(book);
        Message msg = workHandler.obtainMessage();
        msg.what = DOWNLOAD_BOOK;
        msg.obj = book;
        workHandler.sendMessage(msg);
    }

    public void stop(Book book) {
        // TODO fix use worker to download
        LogHelper.warn(TAG, "stop :" + book.getId() + " " + book.getTitle());
        addStoppedBook(book);
    }

    public void delete(Book book) {
        stop(book);
        Message msg = workHandler.obtainMessage();
        msg.what = DELETE_BOOK;
        msg.obj = book;
        workHandler.sendMessage(msg);
    }

    public BookTask getBookTaskStatus(Book book) {
        return null;
    }

    private void handleDownloadBook(Book book) {
        LogHelper.warn(TAG, "start download book:" + book.getId() + " " + book.getTitle());
        prepareDownload(book);
        List<Volume> volumes = DBHelper.getInstance().getBookVolumes(book);
        if (volumes == null || volumes.size() == 0) {
            LogHelper.e(TAG, "download error, volume empty,book:" + book.getId() + " " + book.getTitle());
            onDownloadError(DownloadEvent.DownloadError.ERROR_BOOK, "");
            return;
        }
        for (Volume volume : volumes) {
            prepareDownload(volume);
        }
    }

    private void handleDownloadChapter(Chapter chapter) {
        LogHelper.warn(TAG, "handle download chapter:" + chapter.getId() + " " + chapter.getTitle());
        if (stoppedBook.contains(chapter.getBookId())) {
            LogHelper.warn(TAG, "book task stop, cancel download chapter:" + chapter.getId());
            return;
        }
        // clear
        DBHelper.getInstance().clearDownloadTask(chapter);
        // analyze
        readParagraphFromServer(chapter);
        if (chapterParagraphs == null || chapterParagraphs.size() == 0) {
            LogHelper.warn(TAG, "can not read paragraph from server :" + chapter.getId() + " " + chapter.getTitle());
            onDownloadError(DownloadEvent.DownloadError.ERROR_PARAGRAPH, "");
            return;
        }


        int totalCount = 1;
        List<ContentTask> imageTasks = new ArrayList<>();
        // IMAGE
        for (Paragraph paragraph : chapterParagraphs) {
            switch (paragraph.getType()) {
                case 0:
                    // ignore string
                    break;
                case 1:
                    // each image is a task
                    totalCount++;
                    ContentTask imageTask = new ContentTask();
                    imageTask.setBookId(chapter.getBookId());
                    imageTask.setVolumeId(chapter.getVolumeId());
                    imageTask.setChapterId(chapter.getId());
                    imageTask.setType(ContentTask.TYPE_IMAGE);
                    imageTask.setUrl(paragraph.getContent());
                    String newImageUrl = generateImageContentSavePath(imageTask);
                    paragraph.setContent(newImageUrl);
                    // convert image url
                    imageTask.setSavePath(newImageUrl);
                    DBHelper.getInstance().save(imageTask);
                    imageTasks.add(imageTask);
                    break;
            }
        }

        // save chapter task
        ChapterTask chapterTask = new ChapterTask();
        chapterTask.setBookId(chapter.getBookId());
        chapterTask.setVolumeId(chapter.getVolumeId());
        chapterTask.setChapterId(chapter.getId());
        chapterTask.setTotalCount(totalCount);
        DBHelper.getInstance().save(chapterTask);

        // all content in one task save to content.txt
        ContentTask contentTask = new ContentTask();
        contentTask.setBookId(chapter.getBookId());
        contentTask.setVolumeId(chapter.getVolumeId());
        contentTask.setChapterId(chapter.getId());
        contentTask.setSavePath(generateStringContentSavePath(contentTask));
        DBHelper.getInstance().save(contentTask);
        saveContent(contentTask.getSavePath(), chapterParagraphs);
        onContentDownloadFinish(contentTask);

        // download image
        for (ContentTask imageTask : imageTasks) {
            saveImage(imageTask);
            onContentDownloadFinish(imageTask);
        }

        onChapterDownloadFinish(chapter);
    }

    private void onChapterDownloadFinish(Chapter chapter) {
        // update book
        Book book = DBHelper.getInstance().findFirstById(Book.class, chapter.getBookId());
        if (book != null) {
            book.setCachedChapterCount(book.getCachedChapterCount() + 1);
            DBHelper.getInstance().save(book);
        }

        if (!stoppedBook.contains(chapter.getBookId())) {
            EventHelper.getSingleton().post(new DownloadEvent.DownloadChapterFinish(chapter.getId()));
        }
    }

    private void handleStopDownloadBook(Book book) {
        LogHelper.warn(TAG, "handleStopDownloadBook :" + book.getId() + " " + book.getTitle());
        addStoppedBook(book);
    }

    private void handleDeleteBook(Book book) {
        // delete file
        FileUtils.deleteQuietly(new File(generateBookDirPath(book)));
        List<Volume> volumes = DBHelper.getInstance().getBookVolumes(book);
        if (volumes != null && volumes.size() > 0) {
            for (Volume volume : volumes) {
                deleteVolume(volume);
            }
        }
        DBHelper.getInstance().clearBookCacheCount(book);
    }

    private void handleDownloadBookCover(Book book) {
        BookTask task = new BookTask();
        String coverSavePath = generateCoverSavePath(book);
        task.setBookId(book.getId());
        task.setCover(coverSavePath);
        saveImage(ImageResourceHelper.generateCoverImageUrl(book.getCover()), coverSavePath);
        DBHelper.getInstance().save(task);
    }

    private void deleteVolume(Volume volume) {
        List<Chapter> chapters = DBHelper.getInstance().getVolumeChapters(volume);
        if (chapters != null && chapters.size() > 0) {
            for (Chapter chapter : chapters) {
                DBHelper.getInstance().clearDownloadTask(chapter);
            }
        }
    }

    private void addStoppedBook(Book book) {
        synchronized (stoppedBook) {
            stoppedBook.add(book.getId());
        }
    }

    private void removeStoppedBook(Book book) {
        synchronized (stoppedBook) {
            stoppedBook.remove(book.getId());
        }
    }

    private void saveContent(String saveDir, List<Paragraph> chapterParagraphs) {
        String contentString = JSON.toJSONString(chapterParagraphs, true);
        try {
            FileUtils.write(new File(saveDir), contentString);
        } catch (IOException e) {
            LogHelper.warn(TAG, "save content error");
            e.printStackTrace();
        }
    }

    // TODO use some third lib sdk to download
    private void saveImage(ContentTask imageTask) {
        saveImage(ImageResourceHelper.generateIllustrationImageUrl(imageTask.getUrl()), imageTask.getSavePath());
    }

    private void saveImage(String remoteUrl, String savePath) {
        HttpURLConnection connection = null;
        InputStream input = null;
        try {
            URL url = new URL(remoteUrl);
            connection = (HttpURLConnection) url
                    .openConnection();
            connection.setDoInput(true);
            connection.connect();
            input = connection.getInputStream();
            FileUtils.copyInputStreamToFile(input, new File(savePath));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(input);
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    private String generateCoverSavePath(Book book) {
        String base = Constants.DATADIR.getAbsolutePath();
        String url = book.getCover();
        String fileName = url.substring(url.lastIndexOf("/"));
        if (TextUtils.isEmpty(fileName)) {
            fileName = MiscUtil.string2MD5(book.getCover());
        }
        return base + "/book/" + "book" + book.getId() + "/" + fileName;
    }

    private String generateImageContentSavePath(ContentTask content) {
        String base = Constants.DATADIR.getAbsolutePath();
        String url = content.getUrl();
        String fileName = url.substring(url.lastIndexOf("/"));
        if (TextUtils.isEmpty(fileName)) {
            fileName = MiscUtil.string2MD5(content.getUrl());
        }
        return base + "/book/" + "book" + content.getBookId() + "/volume" + content.getVolumeId() + "/chapter" + content.getChapterId() + "/" + fileName;
    }

    private String generateBookDirPath(Book book) {
        String base = Constants.DATADIR.getAbsolutePath();
        return base + "/book/" + "book" + book.getId();
    }

    private String generateStringContentSavePath(ContentTask content) {
        String base = Constants.DATADIR.getAbsolutePath();
        return base + "/book/" + "book" + content.getBookId() + "/volume" + content.getVolumeId() + "/chapter" + content.getChapterId() + "/content.txt";
    }

    private void readParagraphFromServer(Chapter chapter) {
        chapterParagraphs = null;
        for (retry = 0; retry < MAX_RETRY; retry++) {
            synchronized (readContentMutex) {
                ApiHelper.getSingleton().getParagraph(TAG, chapter.getContentUrl(), readContentCallback);
                // wait response
                try {
                    readContentMutex.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (chapterParagraphs != null) {
                    break;
                }
            }
        }
    }

    private void prepareDownload(Volume volume) {
        LogHelper.warn(TAG, "prepare download volume:" + volume.getId() + " " + volume.getTitle());
        List<Chapter> chapters = DBHelper.getInstance().getVolumeChapters(volume);
        if (chapters == null || chapters.size() == 0) {
            LogHelper.warn(TAG, "prepareDownload volume error, empty volume:" + volume.getId() + " " + volume.getTitle());
            return;
        }
        for (Chapter chapter : chapters) {
            ChapterTask chapterTask = DBHelper.getInstance().getChapterTask(chapter);
            if (chapterTask == null || chapterTask.getFinishCount() < chapterTask.getTotalCount()) {
                prepareDownload(chapter);
            } else {
                LogHelper.warn(TAG, "chapter finish, skip download:" + chapter.getId() + " " + chapter.getTitle());
            }
        }
    }

    private void prepareDownload(Chapter chapter) {
        // async download chapter for network IO delaying
        Message msg = workHandler.obtainMessage();
        msg.what = DOWNLOAD_CHAPTER;
        msg.obj = chapter;
        workHandler.sendMessage(msg);
    }

    private void prepareDownload(Book book) {
        Message msg = workHandler.obtainMessage();
        msg.what = DOWNLOAD_BOOK_COVER;
        msg.obj = book;
        workHandler.sendMessage(msg);
    }

    private void onContentDownloadFinish(ContentTask contentTask) {
        // update chapter task
        ChapterTask chapterTask = DBHelper.getInstance().getChapterTask(contentTask);
        if (chapterTask != null) {
            chapterTask.setFinishCount(chapterTask.getFinishCount() + 1);
            DBHelper.getInstance().save(chapterTask);
            if (!stoppedBook.contains(contentTask.getBookId())) {
                EventHelper.getSingleton().post(
                        new DownloadEvent.DownloadContentProgress(contentTask.getChapterId(), chapterTask.getTotalCount(), chapterTask.getFinishCount())
                );
            } else {
                LogHelper.warn(TAG, "book delete " + contentTask.getBookId() + " do not post message");
            }
            if (chapterTask.getTotalCount() == chapterTask.getFinishCount()) {
                LogHelper.warn(TAG, "download finish:" + chapterTask.getChapterId());
            }
        }
    }

    public void onDownloadError(int code, String msg) {
        EventHelper.getSingleton().post(new DownloadEvent.DownloadError(code, msg));
    }

    class WorkHandler extends Handler {
        public WorkHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DOWNLOAD_BOOK:
                    handleDownloadBook((Book) msg.obj);
                    break;
                case DOWNLOAD_CHAPTER:
                    handleDownloadChapter((Chapter) msg.obj);
                    break;
                case STOP_DOWNLOAD_BOOK:
                    handleStopDownloadBook((Book) msg.obj);
                    break;
                case DELETE_BOOK:
                    handleDeleteBook((Book) msg.obj);
                    break;
                case DOWNLOAD_BOOK_COVER:
                    handleDownloadBookCover((Book) msg.obj);
                    break;
            }
        }
    }

    public static class DownloadEvent {
        public static class DownloadError {
            public static final int ERROR_BOOK = 1;
            public static final int ERROR_PARAGRAPH = 2;
            public int code;
            public String msg;

            public DownloadError(int code, String msg) {
                this.code = code;
                this.msg = msg;
            }
        }

        public static class DownloadContentProgress {
            public int chapterId;
            public int total;
            public int finish;

            public DownloadContentProgress(int chapterId, int total, int finish) {
                this.chapterId = chapterId;
                this.total = total;
                this.finish = finish;
            }
        }

        public static class DownloadChapterFinish {
            public int chapterId;

            public DownloadChapterFinish(int chapterId) {
                this.chapterId = chapterId;
            }
        }
    }

    private class ReadContentCallback extends ParagraphCallback {

        @Override
        public void onSuccess(List<Paragraph> paragraphList) {
            synchronized (readContentMutex) {
                chapterParagraphs = paragraphList;
                readContentMutex.notifyAll();
            }
        }

        @Override
        public void onFailure(int code, String msg) {
            synchronized (readContentMutex) {
                chapterParagraphs = null;
                readContentMutex.notifyAll();
            }
        }
    }

}
